前面幾天,我們已經把 Framework 需要的很多核心概念都拆開來理解了。
有 HttpRequest。
有 HttpResponse。
有 Routing。
有 Handler。
有 Middleware。
有 Parameter Binding。
有 Result Conversion。
但現在真正回頭看專案,最大的問題反而變成:
這些東西最後要怎麼放回程式裡?
如果所有功能還是全部塞在 Program.cs,那即使功能都做出來了,也很難說我們真的做出了一個 Framework。
因為 Framework 的重點不只是:
程式可以跑。
而是:
不同責任要有清楚的邊界。
所以 Day26 的第一個目標,不是再加新功能。
而是正式開始重構專案結構。
今天想解決的問題就是:
怎麼把目前集中在 Program.cs 裡的工作,拆成比較合理的 Framework 元件?
一開始做 Server 時,Program.cs 其實很單純。
大概只是:
建立 TcpListener
Start
AcceptTcpClient
Read
這種程度。
所以全部放在 Program.cs 完全沒問題。
但後來功能一路增加:
解析 Request
Routing
Handler
Response
404
HttpRequest
HttpResponse
Query
Headers
Body
Middleware
如果一直全部放在同一個地方,Program.cs 就會開始變成:
什麼都知道。
什麼都負責。
這樣其實會很難維護。
因為只要改一個功能,就可能影響其他功能。
所以今天的第一個重點是:
把「Framework 本身」和「使用 Framework 的程式」分開。
Program.cs 到底應該負責什麼?
理想上,Program.cs 不應該再直接處理底層 HTTP 細節。
它應該比較像 Framework 的使用入口。
也就是只做:
建立應用程式
註冊 Route
註冊 Middleware
啟動 Server
例如概念上可以變成:
建立 app
註冊 /hello
註冊 /products
啟動
而不是:
AcceptTcpClient
NetworkStream.Read
Split Request Line
Encoding
Write
這些細節。
換句話說:
Program.cs 應該負責「設定」
Framework 內部才負責「執行」。
第一個要拆出去的是什麼?
我覺得最適合先拆出去的是 Server。
因為目前 Program.cs 最底層的一大段工作其實都在做:
建立 TcpListener
等待 Client
讀取 bytes
把 Request 交給後續處理
這些都不是應用程式本身的商業邏輯。
所以可以先想像建立:
MiniWebServer
它負責:
Start
Listen
Accept
Read Request
Send Response
這樣 Program.cs 就不需要再碰太多 TcpListener 細節。
HttpRequest 繼續負責什麼?
HttpRequest 目前的定位可以維持:
描述這次 Request。
例如:
Method
Path
Version
Headers
Query
Body
以及:
Parse
所以:
Server
↓
收到 Raw HTTP
↓
交給 HttpRequest.Parse()
↓
得到 HttpRequest
這樣 Server 不需要知道 Request Line 裡每一個欄位細節。
Router 又放哪裡?
Router 可以獨立負責:
註冊 Route
查找 Route
例如:
/hello
↓
Hello Handler
/products
↓
Products Handler
所以 Server 收到 HttpRequest 後:
HttpRequest
↓
Router.Match(...)
↓
找到 Handler
這樣 Server 也不需要自己一直 if / else。
HttpResponse 則繼續負責什麼?
HttpResponse 已經很適合負責:
StatusCode
StatusText
Headers
Body
Send()
所以最後:
Handler
↓
產生 HttpResponse
↓
HttpResponse.Send()
這樣 Response 的格式和 NetworkStream 寫入細節,就不用散落在其他地方。
那 Middleware 怎麼辦?
Middleware 目前先不用一次做很複雜。
Day26 的重點是先把主架構拆開。
所以可以先保留一個最小想法:
Request
↓
Server
↓
Middleware Pipeline
↓
Router
↓
Handler
↓
HttpResponse
如果今天還沒把 Middleware 實作完整,也沒關係。
重點是先讓專案結構有空間可以放它。
專案結構可以先變成什麼?
目前可以先規劃成:
MiniWebFramework
Program.cs
HttpRequest.cs
HttpResponse.cs
Router.cs
MiniWebServer.cs
之後如果需要:
Middleware
ParameterBinder
ResultConverter
再逐步加入。
這次不是為了把檔案變多。
而是為了讓每個檔案只負責一件主要事情。
MiniWebServer 負責:
Server lifecycle。
HttpRequest 負責:
Request。
HttpResponse 負責:
Response。
Router 負責:
Routing。
Program.cs 負責:
使用 Framework。
這樣責任就開始清楚很多。
Framework 和 Application 開始分開
這一步其實很重要。
因為前面我們做的程式,大多是:
Framework 實作
和
應用程式邏輯
全部混在一起。
例如:
/hello
/products
這些其實是 Application 的 Route。
但:
TcpListener
Request Parsing
Response Formatting
這些則是 Framework 的工作。
今天開始要把兩者分開。
也就是:
Application
↓
告訴 Framework 有哪些 Route
Framework
↓
負責收到 Request 後找到 Route 並執行
這個角色分離,會讓整個專案更像真的 Framework。
為什麼這樣才像 Framework?
因為 Framework 的使用者不應該需要知道:
底層 TCP 怎麼運作。
他只需要知道:
我要註冊什麼 Route。
例如概念上:
MapGet("/hello", ...)
真正收到 Request 後:
Accept Client
Parse Request
Match Route
Invoke Handler
Send Response
全部由 Framework 處理。
這就是 Framework 的價值。
把複雜度藏在內部。
只把必要的介面留給使用者。
以前的我:
Program.cs
↓
整個 Server
現在的我:
Program.cs
↓
Framework 使用入口
Framework 內部
├── MiniWebServer
├── HttpRequest
├── Router
└── HttpResponse
這代表 Program.cs 不再等於 Framework。
它只是:
使用 Framework 的地方。
真正 Framework 的能力,開始移到自己的 Class 裡。
今天最大的發現是:
「做出 Framework」和「做出可以跑的 Server」不是完全一樣的事情。
一個可以跑的 Server,可以全部寫在一支 Program.cs。
但 Framework 需要額外思考:
哪些細節應該被隱藏?
哪些 API 應該暴露?
哪個元件負責哪件事?
使用者應該怎麼操作?
這些問題才真正開始碰到 Framework Design。
最後成品的第一步,不是立刻加入更多功能。
而是先把目前巨大 Program.cs 裡不同責任拆開。
讓 Server、Request、Router、Response 都各自有清楚的位置。
接著才有辦法在後面幾天把 Middleware、Binding、Result Conversion 等功能慢慢接回來。
🛠 與 Mini Web Framework 的關聯
Day26 開始,專案要正式從:
一支可以跑的 Server 程式
變成:
一組可以被使用的 Framework 元件。
方向可以先變成:
Program.cs
↓
設定 Framework
MiniWebServer
↓
啟動 TCP Server
HttpRequest
↓
解析 Request
Router
↓
找 Handler
HttpResponse
↓
送 Response
最後整個流程:
Browser
↓
MiniWebServer
↓
HttpRequest
↓
Router
↓
Handler
↓
HttpResponse
↓
Browser
這會成為最後幾天整合的基礎。
今天的實作目標
今天實作時,我不打算一次完成所有 Framework 元件。
Day26 的目標只需要做到:
第一,把 Server 相關程式從 Program.cs 移出去。
第二,建立 Router 類別。
第三,讓 Program.cs 開始變簡單。
第四,確認 /hello、/products、404 仍然正常。
也就是:
重構前功能正常
↓
拆檔案
↓
重構後功能仍然正常
這就是 Day26 最重要的驗證。
因為今天不是在增加功能。
而是在改善 Framework 的結構。
結尾
前面 25 天,我一直在問:
Framework 為什麼需要這個?
為什麼需要那個?
到了 Day26,問題終於開始變成:
我真的要怎麼把它做出來?
今天第一步不是寫更多功能。
而是把前面累積在 Program.cs 裡的責任重新整理。
Server 做 Server 的事。
Request 做 Request 的事。
Router 做 Routing。
Response 做 Response。
Program.cs 則開始只負責告訴 Framework:
我要哪些 Route。
我要怎麼啟動。
如果這一步可以完成,代表我們不再只是在:
寫一個 Server。
而是真的開始:
設計一個 Framework。
當 Server、Request、Router、Response 都拆開之後,下一個問題就是:
它們真的可以順利合作嗎?
Framework 收到 Request 後,怎麼把:
HttpRequest
↓
Router
↓
Handler
↓
HttpResponse
真正串成一條可以運作的流程?